package com.fw.payment.service;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.integration.handler.ReplyRequiredException;

public class ServiceExecutor {

    private Logger log = LoggerFactory.getLogger(ServiceExecutor.class);

    private long timeout;
    private ServiceOrder serviceOrder;

    public static int count = 0;

    public ServiceExecutor(ServiceOrder serviceOrder, long timeout) {
	this.serviceOrder = serviceOrder;
	this.timeout = timeout;
    }

    public ServiceOrder callService() throws Exception {
	ExecutorService executor = null;
	FutureTask<ServiceOrder> future = null;
	try {
	    executor = Executors.newSingleThreadExecutor();
	    future = new FutureTask<ServiceOrder>(new Callable<ServiceOrder>() {

		@Override
		public ServiceOrder call() throws Exception {
		    try {
			if (serviceOrder != null) {
			    log.info("CALL SERVICE[ID:" + serviceOrder.getObjId() + ", CLASS:" + serviceOrder.getServiceClass().getClass()
				    + ", METHOD:" + serviceOrder.getMethodName());
			    Method eventMethod = serviceOrder.getServiceClass().getClass()
				    .getMethod(serviceOrder.getMethodName(), serviceOrder.getParameterTypes());
			    // Reflect - call eventMethod of subclass
			    Object result = eventMethod.invoke(serviceOrder.getServiceClass(), serviceOrder.getParameterValues());
			    if (eventMethod.getReturnType().toString().equals("void")) {
				serviceOrder.setResultCode(0);
				serviceOrder.setResultString(null);
				serviceOrder.setReturnValue(null);
			    } else {
				if (result != null) {
				    serviceOrder.setResultCode(0);
				    serviceOrder.setResultString(null);
				    serviceOrder.setReturnValue(result);
				}
			    }
			    count++;
			    log.info("RESULT SERVICE[ID:" + serviceOrder.getObjId() + ", RESULT CODE:" + serviceOrder.getResultCode() + "]");
			} else {
			    log.error("CALL SERVICE FAILED[ID:" + serviceOrder.getObjId());
			}
		    } catch (Exception ex) {
			log.error("CALL SERVICE FAILED[ID:" + serviceOrder.getObjId() + "] MESSAGE:" + ex.getMessage(), ex);
		    }
		    return serviceOrder;
		}
	    });

	    log.info("EXECUTE TASK[ID:" + this.serviceOrder.getObjId() + "]");
	    executor.execute(future);
	    serviceOrder = future.get(this.timeout, TimeUnit.SECONDS);
	    while (!future.isDone()) {
	    }
	    if (serviceOrder != null) {
		log.info("EXECUTE TASK SUCCESSED[ID:" + this.serviceOrder.getObjId() + "]");
	    } else {
		log.info("EXECUTE TASK FAILED[ID:" + this.serviceOrder.getObjId() + "] NO RETURN RESULT");
	    }
	} catch (ExecutionException e) {
	    log.error("EXECUTE TASK ERROR EXECUTION[ID:" + this.serviceOrder.getObjId() + "] MESSAGE:" + e.getMessage(), e);
	    throw e;
	} catch (InterruptedException e) {
	    log.error("EXECUTE TASK ERROR INTERRUPTED[ID:" + this.serviceOrder.getObjId() + "] MESSAGE:" + e.getMessage(), e);
	    throw e;
	} catch (TimeoutException e) {
	    log.error("EXECUTE TASK ERROR TIMEOUT[ID:" + this.serviceOrder.getObjId() + "] MESSAGE:" + e.getMessage(), e);
	    try {
		log.info("EXECUTE CANCEL TASK[ID:" + this.serviceOrder.getObjId() + "]");
		future.cancel(true);
	    } catch (Exception ex) {
		log.error("EXECUTE CANCEL TASK ERROR[ID:" + this.serviceOrder.getObjId() + "]");
		log.error("EXECUTE CANCEL TASK ERROR", ex);
	    }
	    throw e;
	} catch (ReplyRequiredException e) {
	    log.error("EXECUTE TASK ERROR ReplyRequiredException :" + e.getMessage());
	} catch (Exception e) {
	    log.error("EXECUTE TASK ERROR Exception :" + e.getMessage());
	} finally {
	    executor.shutdown();
	    log.info("EXECUTE TASK SHUTDOWN[ID:" + this.serviceOrder.getObjId() + "]");
	}
	return serviceOrder;
    }

    public void setServiceOrder(ServiceOrder serviceOrder) {
	this.serviceOrder = serviceOrder;
    }

    public ServiceOrder getServiceOrder() {
	return serviceOrder;
    }

}
